import copy
import json


class AttributedObject(object):

    ### INITIALIZER ###

    def __init__(self):
        self._ids_are_current = False

    ### READ-ONLY PUBLIC PROPERTIES ###

    @property
    def improper_parentage(self):
        node = self
        parentage = [node]
        while hasattr(node, 'patcher'):
            if node.patcher is not None:
                parentage.append(node.patcher)
            node = node.patcher
        return tuple(parentage)

    @property
    def json_data(self):
        self._update_ids_of_entire_tree_if_necessary()
        return self._get_json_data()

    @property
    def json_format(self):
        return json.dumps(self.json_data, indent=4, sort_keys=True)

    ### PRIVATE METHODS ###

    def _get_patcher_id_state(self):
        ids_are_current = True
        for obj in self.improper_parentage:
            if ids_are_current:
                if not obj._ids_are_current:
                    ids_are_current = False
        return ids_are_current

    def _mark_entire_tree_for_later_update(self):
        for obj in self.improper_parentage:
            obj._ids_are_current = False

    def _update_ids_of_entire_tree(self):
        if hasattr(self, 'patcher'):
            root = self.root_patcher
        elif hasattr(self, 'boxes'):
            root = self
        def recurse(patcher, count=0):
            patcher._ids_are_current = True
            for box in patcher.boxes:
                box._ids_are_current = True
                count += 1
                box._id = count
                if hasattr(box, 'boxes'):
                    count = recurse(box, count)
            return count
        if root is not None:
            count = recurse(root)
        else:
            self._ids_are_current = True

    def _update_ids_of_entire_tree_if_necessary(self):
        ids_are_current = self._get_patcher_id_state()
        if not ids_are_current:
            self._update_ids_of_entire_tree()
